Skip to content

Enable multi-environment publish to shared APIM instance - #227

Open
petehauge wants to merge 3 commits into
mainfrom
feat/multi-env-shared-apim
Open

Enable multi-environment publish to shared APIM instance#227
petehauge wants to merge 3 commits into
mainfrom
feat/multi-env-shared-apim

Conversation

@petehauge

@petehauge petehauge commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds multi-environment publish to a single shared Azure APIM instance via a new optional environment: block in configuration.<env>.yaml override files. Resource names are namespaced by namePrefix and/or nameSuffix, and API paths can be scoped by apiPathPrefix.

Related Issue(s)

Closes #132

What's included

New capability

  • environment: block in override files — configures per-environment name and path affixing so dev, qa, and prod can share one APIM
  • --delete-unmatched is now namespace-scoped when environment: is configured — a dev publish cannot delete prod resources on the shared instance
  • Automatic policy XML rewriting{{namedValue}} tokens, fragment-id, and backend-id attributes are rewritten to affixed names at publish time
  • Cross-resource reference rewriting — subscription scopes, apiRelease apiIds, MCP tool operation ids, and PolicyFragment references

Safety guardrails

  • Hard-block when environment: is present but both namePrefix and nameSuffix are missing/empty — path-only mode is intentionally unsupported because --delete-unmatched would silently destroy other environments' resources
  • Character validation on affixes ([A-Za-z0-9-] only)
  • Whitespace-only affixes rejected (trimmed to empty → error)
  • appliesTo validation catches unknown or non-affixable resource types

New files

  • src/services/env-mapper.ts — pure name/descriptor mapping module
  • src/services/env-mapping-validator.ts — pre-flight validator with hard-block for unsafe configurations
  • src/services/policy-ref-rewriter.ts — regex-based policy XML ref rewriter
  • docs/guides/multi-environment-shared-apim.md — new 440-line user guide with setup, safety notes, examples, and troubleshooting

Modified files

  • src/services/publish-service.ts — orchestration wiring; incremental-mode uses full listResources for validator and known-artifact set
  • src/services/resource-publisher.tsmapDescriptor applied at 5 PUT boundaries; 4 normalizers extended; NamedValue displayName / Api path prefix / PolicyFragment content / policy XML rewriting
  • src/services/delete-unmatched-service.ts — namespace scoping via toCanonicalDescriptor
  • src/models/config.tsEnvironmentOverride, KnownArtifactSets, extended PublishConfig
  • src/lib/config-loader.ts, src/services/override-merger.ts — schema-loader wiring
  • schemas/v1/override-config.schema.json — additive schema extension (v1 stays v1)
  • src/templates/configs/override-config.yaml, src/templates/copilot/configure-overrides-prompt.md — init scaffold
  • README.md, docs/guides/environment-overrides.md — cross-links to new guide

Tests

  • 6 new test files (env-mapper, policy-ref-rewriter, config.environment schema, delete-unmatched env-mapping, publish-service env-mapping, resource-publisher env-mapping)
  • 4 existing test files extended
  • 130+ new tests total; all 1234 tests pass

Design decisions (locked in with @petehauge)

  1. Both name and displayName are affixed for NamedValues so {{token}} references in policies work across environments
  2. Regex-based policy XML rewriting for {{token}} + targeted fragment-id/backend-id attributes (no full XML parse)
  3. Explicit per-API path override wins over apiPathPrefix
  4. Documentation, GlobalSchema, and PolicyRestriction are opt-in only (not in DEFAULT_APPLIES_TO)
  5. Schema stays v1 (additive change, no version bump)
  6. Path-only mode is blocked at validation time (see safety guardrails)

Reviews

Four internal squad reviews performed on this branch:

  • CodeReviewer: APPROVE (one non-blocking wording nit)
  • SecurityExpert: CLEAR (no findings)
  • ApiOpsLead: APPROVE-WITH-CONCERNS (required fix applied: import.meta.url in test)
  • ApimExpert: APPROVE-WITH-CONCERNS

Testing

npm test          # 1234/1234 pass (excluding known pre-existing Windows CLI-help flake)
npm run lint      # clean
npx tsc --noEmit  # clean

Add an optional `environment:` block to `configuration.<env>.yaml` override
files that enables publishing dev, qa, and prod to a single shared Azure API
Management instance. Resource names are namespaced via `namePrefix` and/or
`nameSuffix`, and API paths can be scoped via `apiPathPrefix`.

Key changes:

- New `env-mapper` module — pure name/descriptor mapping (canonical <-> deployed)
- New `env-mapping-validator` — pre-flight validation with hard-block when the
  environment block is present but lacks any name affix (path-only mode is
  intentionally unsupported because --delete-unmatched would destroy other
  environments' resources on the shared instance)
- New `policy-ref-rewriter` — regex-based rewrite of {{namedValue}} tokens,
  fragment-id, and backend-id attributes in policy XML
- Publisher integration at all 5 PUT boundaries with mapDescriptor
- Normalizers extended for subscription scopes, apiRelease apiIds, MCP tool
  operation ids, and named-value references
- NamedValue displayName affixing, Api path prefixing, PolicyFragment content
  rewriting, and policy XML rewriting
- delete-unmatched-service namespace-scoped via toCanonicalDescriptor: a dev
  publish cannot delete prod resources sharing the same APIM
- Schema v1 extended additively (no version bump) with `environment` property
- init scaffold and Copilot prompt updated
- New user guide: docs/guides/multi-environment-shared-apim.md

Closes #132

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@petehauge petehauge changed the title feat: multi-environment publish to shared APIM instance Enable multi-environment publish to shared APIM instance Jul 6, 2026
@petehauge
petehauge requested review from EMaher and Copilot July 6, 2026 06:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for publishing multiple environments (dev/qa/prod) into a single shared Azure API Management instance by introducing an optional environment: block in override configs, applying name/path affixes at publish time, rewriting cross-resource references, and scoping --delete-unmatched to the active environment namespace.

Changes:

  • Introduce env mapping (prefix/suffix + appliesTo + apiPathPrefix) with runtime validation and wire it through publish, resource publishing, and delete-unmatched.
  • Add policy XML reference rewriting ({{namedValue}}, fragment-id, backend-id) and other cross-resource reference rewrites (subscription scope, apiRelease apiId, MCP tool operationIds).
  • Extend templates/schema/docs and add comprehensive unit test coverage for the new behavior.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/unit/templates/configs/config-templates.test.ts Asserts override template includes commented environment: block and substitutions.
tests/unit/services/resource-publisher.env-mapping.test.ts Validates env-mapping behavior at PUT boundaries and reference rewrites.
tests/unit/services/publish-service.test.ts Adds regression coverage for incremental mode using full artifact set for env mapping / known artifacts.
tests/unit/services/publish-service.env-mapping.test.ts Unit tests for env-mapping validation behavior and warnings/errors.
tests/unit/services/policy-ref-rewriter.test.ts Unit tests for policy XML token/attribute rewrites with known-sets gating.
tests/unit/services/override-merger.test.ts Verifies deep-merge behavior for logger credentials overrides.
tests/unit/services/env-mapper.test.ts Unit tests for mapping primitives (affixing, canonicalization, descriptor mapping).
tests/unit/services/delete-unmatched.env-mapping.test.ts Ensures delete-unmatched is namespace-scoped under env mapping.
tests/unit/models/config.environment.test.ts Validates schema acceptance/rejection for the new environment field.
tests/unit/lib/config-loader.test.ts Ensures YAML override loader parses/validates environment overrides at runtime.
src/templates/copilot/configure-overrides-prompt.md Updates Copilot prompt to guide users on shared-APIM vs dedicated setups.
src/templates/configs/override-config.yaml Adds commented environment: block scaffold and doc link.
src/services/resource-publisher.ts Applies env mapping at PUT boundaries; adds known-artifact sets; rewrites policy refs and other cross-resource references.
src/services/publish-service.ts Builds env mapping + known artifact sets (uses full artifact list in incremental mode).
src/services/policy-ref-rewriter.ts New policy XML reference rewriter (regex-based with known-sets gating).
src/services/override-merger.ts Tightens typing for override section keys (excludes non-section fields like environment).
src/services/env-mapping-validator.ts New validator enforcing safe shared-APIM configuration and logging warnings.
src/services/env-mapper.ts New mapping utilities for deployed/canonical naming and descriptor segment mapping.
src/services/delete-unmatched-service.ts Scopes deletions to current env namespace and compares canonical names against local artifacts.
src/models/config.ts Adds EnvironmentOverride, KnownArtifactSets, and publish-time envMapping fields.
src/lib/config-loader.ts Parses environment: override block and warns on unknown keys.
scripts/generate-schemas.mjs Extends schema generator to include environmentOverride definition.
schemas/v1/override-config.schema.json Adds environment property + environmentOverride definition.
README.md Adds guide links for environment overrides and shared-APIM publishing.
package.json Adds ajv dev dependency for schema validation tests.
package-lock.json Updates lockfile for ajv dependency resolution.
docs/guides/multi-environment-shared-apim.md New guide documenting shared-APIM workflow, safety model, and limitations.
docs/guides/environment-overrides.md Adds section linking to shared-APIM guidance and clarifying canonical naming.

Comment thread tests/unit/services/publish-service.test.ts Outdated
Comment thread src/services/env-mapper.ts Outdated
Comment thread src/services/delete-unmatched-service.ts Outdated
Peter Hauge and others added 2 commits July 5, 2026 23:16
…imit

Top-level APIM resource names (APIs, NamedValues, Products, Backends, etc.)
are capped at 80 characters. When the environment name affix pushes a
deployed name past that threshold, the ARM PUT fails mid-publish with a
cryptic 400 that does not identify the affix as the cause.

The validator now iterates over artifact descriptors whose type is in
`appliesTo` and emits a logger.warn (not an error) when
`namePrefix + canonicalName + nameSuffix` exceeds 80 characters. The warning
includes the offending deployed name, the length, the resource type, and
remediation guidance. It fires during `apiops publish --dry-run` too.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- env-mapper: consolidate EnvironmentOverride to a single definition in
  src/models/config.ts; drop the unsafe `as EnvironmentOverride | undefined`
  cast; buildEnvMapping now safely converts string[] appliesTo values to
  a ReadonlySet<ResourceType> via a validated filter. The prior duplicate
  interface let string[] and ResourceType[] shapes drift silently.
- delete-unmatched-service: trim trailing whitespace on the "prefix dropped
  mid-life" doc comment.
- publish-service test: split `async () => { const client = ...` onto two
  lines to match surrounding style.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multi-environment deployment to a single APIM instance

3 participants